/****************************************************************************
 * Copyright (C) 2011 Maxim Integrated Products, All Rights Reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY,  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 * IN NO EVENT SHALL MAXIM INTEGRATED PRODUCTS BE LIABLE FOR ANY CLAIM, DAMAGES
 * OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 *
 * Except as contained in this notice, the name of Maxim Integrated Products
 * shall not be used except as stated in the Maxim Integrated Products
 * Branding Policy.
 *
 * The mere transfer of this software does not imply any licenses
 * of trade secrets, proprietary technology, copyrights, patents,
 * trademarks, maskwork rights, or any other form of intellectual
 * property whatsoever. Maxim Integrated Products retains all ownership rights.
 *
 *     Description: MAXQ SPI driver
 *             $Id: maxq_spi1.c 3272 2012-02-27 20:28:47Z jbrodt $
 *
 ********************************************************************************
 */

#include <stddef.h>
#include "maxq_config.h"
#include "maxq_spi.h"
#include "maxq_spi_priv.h"

#if (NUM_SPI > 1)

/***** Definitions *****/


/***** Function Prototypes *****/


/***** Global Data *****/
spi_t spi1;


/***** File Scope Data *****/
#if (SPI1_RXB_LEN > 0)
static fifo_t rxf; // receive fifo
static uint16_t spi_rxbuf[SPI1_RXB_LEN]; // receive buffer
#endif
#if (SPI1_TXB_LEN > 0)
static fifo_t txf; // transmit fifo
static uint16_t spi_txbuf[SPI1_TXB_LEN]; // transmit buffer
#endif


/****************************************************************************/
void spi1_master_init(spi_master_config_t * config)
{
  SPICN1 = 0x02;  // configure SPI in master mode, clear all flags

  if (config->modefault == SPI_MODE_FAULT_ENABLED)
    SPICN1 |= 0x04; // set MODFE bit

  SPICF1 = 0x00;  // clear existing settings

  if (config->polarity == SPI_POLARITY_FALLING)
    SPICF1 |= 0x01; // set CKPOL bit

  if (config->phase == SPI_PHASE_INACTIVE)
    SPICF1 |= 0x02; // set CKPHA bit
  
  if (config->length == SPI_LEN_16)
    SPICF1 |= 0x04; // set CHR bit
  
  SPICK1 = config->baudclkdiv;

  SPIB1 = 0x00;  // clear buffer register
  
#if (SPI1_RXB_LEN > 0)
  fifo_init(&rxf, spi_rxbuf, SPI1_RXB_LEN);
  spi1.rxf = &rxf;
#else
  spi1.rxf = NULL;
#endif
#if (SPI1_TXB_LEN > 0)
  fifo_init(&txf, spi_txbuf, SPI1_TXB_LEN);
  spi1.txf = &txf;
#else
  spi1.txf = NULL;
#endif

  spi1.isr_callback = NULL;
  spi1.err_callback = NULL;
  spi1.fifo_callback = NULL;
  spi1.ssel_callback = NULL;  
}

/****************************************************************************/
void spi1_slave_init(spi_slave_config_t * config)
{
  SPICN1 = 0x00;  // configure SPI in slave mode, clear all flags
  SPICF1 = 0x00;  // clear existing settings

  if (config->polarity == SPI_POLARITY_FALLING)
    SPICF1 |= 0x01; // set CKPOL bit

  if (config->phase == SPI_PHASE_INACTIVE)
    SPICF1 |= 0x02; // set CKPHA bit
  
  if (config->length == SPI_LEN_16)
    SPICF1 |= 0x04; // set CHR bit
  
  if (config->ssel_active == SPI_SSEL_HIGH)
    SPICF1 |= 0x40; // set SAS bit
  
  SPIB1 = 0x00;  // clear buffer register

#if (SPI1_RXB_LEN > 0)
  fifo_init(&rxf, spi_rxbuf, SPI1_RXB_LEN);
  spi1.rxf = &rxf;
#else
  spi1.rxf = NULL;
#endif
#if (SPI1_TXB_LEN > 0)
  fifo_init(&txf, spi_txbuf, SPI1_TXB_LEN);
  spi1.txf = &txf;
#else
  spi1.txf = NULL;
#endif

  spi1.isr_callback = NULL;
  spi1.err_callback = NULL;
  spi1.fifo_callback = NULL;
  spi1.ssel_callback = NULL;  
}

/****************************************************************************/
void spi1_deinit(void)
{
  if (SPICN1 & ENABLE_MASK) { // if SPI is currently enabled
    if (spi1.txf != NULL) {
    while (!fifo_empty(spi1.txf)); // wait for all transfers to complete
    }
    while (SPICN1 & STBY_FLAG); // wait for SPI to complete previous operation
  }

  SPICN1 = 0x00;  // disable and clear all flags and settings
  SPICF1 = 0x00;  // clear existing settings
  SPICK1 = 0x00;  // clear existing settings
}

/****************************************************************************/
void spi1_change_baud(uint8_t baudclkdiv)
{
  uint8_t istate;

  // atomic access required to change baudrate
  istate = IC;  // save interrupt enabled state
  IC &= ~INT_GLOBAL_ENABLE;  // disable interrupts globally

  if (SPICN1 & ENABLE_MASK) { // if SPI is currently enabled
    if (spi1.txf != NULL) {
    while (!fifo_empty(spi1.txf)); // wait for all transfers to complete
    }
    while (SPICN1 & STBY_FLAG); // wait for SPI to complete previous operation
    SPICN1 &= ~ENABLE_MASK;     // disable SPI
    SPICK1 = baudclkdiv;        // set new baudrate
    SPICN1 &= ~ENABLE_MASK;     // re-enable SPI
  }
  else {
    SPICK1 = baudclkdiv;    // set new baudrate
  }

  IC |= (istate & INT_GLOBAL_ENABLE); // restore interrupt enabled state
}

/****************************************************************************
* Description: Send & receive 1 word over SPI
* Parameters: data to send
* Returns: data received
****************************************************************************/
uint16_t spi1_sendrecv(uint16_t data)
{
  spi1_send(data);
  while (SPICN1 & STBY_FLAG); // wait for transfer to complete
  return spi1_recv();
}


/****************************************************************************
* Description: Sends 1 word over SPI, does not wait for completion to return
* Parameters: data to send
* Returns: void
****************************************************************************/
void spi1_send(uint16_t data)
{
  while (SPICN1 & STBY_FLAG);  // wait for SPI to complete previous operation

  // if master mode (MSTM bit set)
  if ( (SPICN1 & MASTER_MASK) && (spi1.ssel_callback != NULL) ) {
    (*(spi1.ssel_callback))(SSEL_ACTIVATE);   // call user callback
  }

  SPICN1 &= ~XFER_COMPLETE_FLAG;  // clear SPIC flag
  SPIB1 = data; // data to send
}

/****************************************************************************/
uint16_t spi1_recv(void)
{
  uint16_t data;
  data = SPIB1; // receive data from buffer
  SPICN1 &= ~XFER_COMPLETE_FLAG;  // clear SPIC flag
  return data;
}

/****************************************************************************/
int spi1_fifo_sendrecv(uint16_t * txdata, uint16_t * rxdata, uint16_t len)
{
  if (!spi1_fifo_send(txdata, len))
    return MAXQ_FAILURE;

  while(spi1_status());   // wait for all transfers to complete

  if (!spi1_fifo_recv(rxdata, len))
    return MAXQ_FAILURE;

  return MAXQ_SUCCESS;
}

/****************************************************************************/
#if (SPI1_TXB_LEN > 0)
int spi1_fifo_send(uint16_t * data, uint16_t len)
{
  int i;
  uint8_t needtostart;
  uint16_t txdata;

  // Check length and FIFO capacity
  if ( (len == 0) || (spi1.txf == NULL) || (len > fifo_remaining(spi1.txf)) )
    return MAXQ_FAILURE;

  // If FIFO is empty and no current transmission, we'll need to initiate 1st transfer
  needtostart = fifo_empty(spi1.txf) && !(SPICN1 & STBY_FLAG);

  // Add to FIFO
  for (i = 0; i < len; i++)
    fifo_put16(spi1.txf, data[i]);

  // Initiate transfer if FIFO was empty
  if (needtostart) {
    spi1_enable_interrupt();  // interrupts needed for FIFO processing
    fifo_get16(spi1.txf, &txdata);
    spi1_send(txdata);
  }

  return MAXQ_SUCCESS;
}
#endif

/****************************************************************************/
#if (SPI1_RXB_LEN > 0)
int spi1_fifo_recv(uint16_t * data, uint16_t len)
{
  int i;
  uint16_t temp;

  // Check if requested data exists in FIFO
  if ( (spi1.rxf == NULL) || (fifo_level(spi1.rxf) < len) )
    return MAXQ_FAILURE;

  // Get from FIFO
  if (data != NULL) {
    for (i = 0; i < len; i++)
      fifo_get16(spi1.rxf, &(data[i]));
  }
  else {  // perform dummy reads
    for (i = 0; i < len; i++)
      fifo_get16(spi1.rxf, &temp);
  }

  return MAXQ_SUCCESS;
}
#endif

/****************************************************************************/
int spi1_status(void)
{
  // Check if messages are currently awaiting transfer completion
  if ( ((spi1.txf != NULL) && !fifo_empty(spi1.txf)) || (SPICN1 & STBY_FLAG) )
    return TRUE;

  return FALSE;
}

/****************************************************************************/
void spi1_isr_callback(void (*func)(void))
{
  spi1.isr_callback = func;
}

/****************************************************************************/
void spi1_err_callback(void (*func)(void))
{
  spi1.err_callback = func;
}

/****************************************************************************/
void spi1_fifo_callback(void (*func)(void))
{
  spi1.fifo_callback = func;
}

/****************************************************************************/
void spi1_ssel_callback(void (*func)(int))
{
  spi1.ssel_callback = func;
}

#endif /* (NUM_SPI > 1) */
